package com.unittec.zk.provider.common;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.servlet.http.HttpServletRequest;

import com.unittec.zk.provider.scope.GlobalScope;
import com.unittec.zk.provider.scope.ThreadLocalScope;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.util.EntityUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.unittec.zk.provider.ApplicationContextProvider;
import com.unittec.zk.sdk.root.common.FieldConstants;
import com.unittec.zk.sdk.root.domain.GatewayRequest;
import com.unittec.zk.sdk.root.domain.Result;

/**
 * @author wuxinwei
 */
public class WebUtils {

    private WebUtils() {}

    public static HttpHeaders buildHttpHeaders(String token) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);

        if (StringUtils.isNotEmpty(token)) {
            headers.add(FieldConstants.REQ_AUTHORIZATION, FieldConstants.REQ_BEARER + token);
        }
        return headers;
    }

    public static String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        return ip;
    }

    /**
     * 这个远程调用方法不支持熔断，推荐用支持熔断的HystrixRestTemplateService
     */
    @Deprecated
    public static <T, M> Result<T> exchange(String url, GatewayRequest<M> request,
                                            ParameterizedTypeReference<Result<T>> result) {
        String token = request.getToken();
        ThreadLocalScope.AuthData authData = ThreadLocalScope.getAuthData();

        if (null == token || token.length() < 8) {
            token = authData.getToken();
        }

        if (null == token || token.length() < 8) {
            token = GlobalScope.getToken(request.getAppId());
        }

        if (null != token && token.indexOf(FieldConstants.REQ_BEARER) > -1) {
            token = token.substring(7);
        }

        MultiValueMap<String, String> values = new LinkedMultiValueMap();
        values.add(FieldConstants.TENANT_ID, authData.getTenantId());
        values.add(FieldConstants.APP_ID, authData.getAppId());
        values.add(FieldConstants.LOGIN_USER, authData.getUser());

        request.setAppId(authData.getAppId());
        request.setRequestId(authData.getRequestId());
        request.setToken(authData.getToken());

        HttpHeaders headers = WebUtils.buildHttpHeaders(token);
        headers.addAll(values);

        RestTemplate restTemplate = ApplicationContextProvider.getBean(RestTemplate.class);

        return restTemplate.exchange(url, HttpMethod.POST,
                new org.springframework.http.HttpEntity<GatewayRequest<M>>(request, headers), result).getBody();
    }

    /**
     * Post 发送 Json 数据
     *
     * @param url
     *            请求地址
     * @param json
     *            JSON 数据
     * @return
     * @throws IOException
     */
    public static String doPostJson(String url, String json) throws IOException {
        HttpPost httpPost = new HttpPost(url);
        StringEntity entity = new StringEntity(json, "UTF-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        HttpResponse execute = HttpClientBuilder.create().build().execute(httpPost);
        if (execute.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            return EntityUtils.toString(execute.getEntity());
        }
        return null;
    }

    public static String etcPostJson(String url, String reqJson, String acc, String pas) {
        // post请求返回结果
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);
        // 设置请求和传输超时时间
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build();
        httpPost.setConfig(requestConfig);
        httpPost.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
        httpPost.setHeader("acc", acc);
        httpPost.setHeader("pas", pas);
        String respStr = "";
        try {
            if (null != reqJson) {
                // 解决中文乱码问题
                StringEntity entity = new StringEntity(reqJson, "utf-8");
                entity.setContentEncoding("UTF-8");
                entity.setContentType("application/json;charset=UTF-8");
                httpPost.setEntity(entity);
            }
            CloseableHttpResponse result = httpClient.execute(httpPost);
            // 请求发送成功，并得到响应
            if (result.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                try {
                    // 读取服务器返回过来的json字符串数据
                    respStr = EntityUtils.toString(result.getEntity(), "utf-8");
                } catch (Exception e) {
                }
            } else {
                respStr = EntityUtils.toString(result.getEntity(), "utf-8");
            }
        } catch (IOException e) {
        } finally {
            httpPost.releaseConnection();
        }
        return respStr;
    }

    public static String doPostKeyValue(String url, Map map, Boolean isRep) throws IOException {
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=\"UTF-8\"");
        String value = "";
        if (isRep) {
            value = getValue(map);
        } else {
            value = getValue2(map);
        }

        StringEntity entity = new StringEntity(value, "UTF-8");
        entity.setContentType("application/x-www-form-urlencoded");
        httpPost.setEntity(entity);
        HttpResponse execute = HttpClientBuilder.create().build().execute(httpPost);
        if (execute.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            return EntityUtils.toString(execute.getEntity());
        }
        return null;
    }

    public static String doPost(String url, Map<String, String> param) {
        // 创建Httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";
        try {

            // 创建uri
            URIBuilder builder = new URIBuilder(url);
            if (param != null) {
                for (String key : param.keySet()) {
                    builder.addParameter(key, param.get(key));
                }
            }
            URI uri = builder.build();
            // 创建Http Post请求
            HttpPost httpPost = new HttpPost(uri);

            // 执行http请求
            response = httpClient.execute(httpPost);
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultString;
    }

    /**
     * Get 请求数据
     *
     * @param url
     *            请求地址
     * @return
     * @throws IOException
     */
    public static String doQueryString(String url, Map<String, String> params) throws IOException {
        HttpClient client = HttpClientBuilder.create().build();
        String reqParams = getUrl(url, params);
        HttpGet get = new HttpGet(reqParams);
        HttpResponse execute = client.execute(get);
        if (execute.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            return EntityUtils.toString(execute.getEntity(), "utf-8");
        }
        return null;
    }

    public static String getUrl(String url, Map<String, String> params) throws IOException {
        StringBuffer sb = new StringBuffer();
        if (url.indexOf("?") == -1) {
            sb.append("?");
        }
        sb.append(getValue(params));

        return url + sb.toString();
    }

    private static String getValue(String value) {
        return value.replace(" ", "%20").replace("+", "%2B");
    }

    private static String getValue(Map<String, String> params) {
        StringBuffer sb = new StringBuffer();
        if (ObjectUtils.isNotNull(params)) {
            for (Map.Entry<String, String> stringStringEntry : params.entrySet()) {
                String value = String.valueOf(stringStringEntry.getValue());

                sb.append(stringStringEntry.getKey() + "=" + (value.contains("-") ? value : getValue(value)) + "&");
            }
            if (sb.toString().endsWith("&")) {
                sb.delete(sb.length() - 1, sb.length());
            }
        }

        return sb.toString();
    }

    private static String getValue2(Map<String, String> params) {
        StringBuffer sb = new StringBuffer();
        if (ObjectUtils.isNotNull(params)) {
            for (Map.Entry<String, String> stringStringEntry : params.entrySet()) {
                String value = String.valueOf(stringStringEntry.getValue());

                sb.append(stringStringEntry.getKey() + "=" + value + "&");
            }
            if (sb.toString().endsWith("&")) {
                sb.delete(sb.length() - 1, sb.length());
            }
        }

        return sb.toString();
    }

    /**
     * 上上签post请求
     *
     * @param sendData
     * @return
     * @throws IOException
     */
    public static String sendHttpPost(String requestUrl, String sendData) throws IOException {

        Map<String, Object> response = request("POST", requestUrl, sendData, null);
        int responseCode = Integer.parseInt(response.get("responseCode").toString());
        byte[] responseBytes = (byte[])response.get("responseData");
        String responseString;
        responseString = new String(responseBytes, StandardCharsets.UTF_8);
        // 请求返回结果无论成功失败，http-status均为200
        if (responseCode == 200) {
            // 返回结果
            return responseString;
        } else {
            throw new IOException(responseCode + ":" + responseString);
        }
    }

    public static byte[] sendHttpGet(String host, String method, String urlParams) throws IOException {
        String requestUrl = host + method + urlParams;
        Map<String, Object> response = request("GET", requestUrl, null, null);
        int responseCode = Integer.parseInt(response.get("responseCode").toString());
        byte[] responseBytes = (byte[])response.get("responseData");
        // 请求返回结果无论成功失败，http-status均为200
        if (responseCode == 200) {
            // 返回结果
            return responseBytes;
        } else {
            throw new IOException(responseCode + "");
        }
    }

    public static String urlencode(String data) {
        return urlencode(data, "UTF-8");
    }

    public static String urlencode(String data, String charset) {
        try {
            return URLEncoder.encode(data, charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public static Map<String, Object> request(String method, String url, Object sendData, Map<String, String> headers)
            throws IOException {

        String requestPath;
        try {
            requestPath = new URL(url).getPath();
        } catch (MalformedURLException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
        CloseableHttpClient httpClient = getHttpClient(requestPath);

        Map<String, Object> response = null;
        if ("POST".equals(method)) {
            response = sendPost(httpClient, url, headers, sendData);
        } else {
            response = sendGet(httpClient, url, headers);
        }
        return response;
    }

    private static Map<String, Object> sendPost(CloseableHttpClient httpClient, String url, Map<String, String> headers,
                                                Object sendData) throws IOException {
        String tag = "[HttpRequester] [POST " + url + "]";
        int responseCode = -1;
        byte[] responseBytes = null;

        HttpPost request = new HttpPost(url);
        if (headers != null && headers.size() > 0) {
            for (String name : headers.keySet()) {
                String value = headers.get(name);
                request.setHeader(name, value);
            }
        }
        if (sendData != null) {
            StringEntity stringEntity = new StringEntity((String)sendData, "UTF-8");
            stringEntity.setContentType("application/json");
            request.setEntity(stringEntity);

            HttpEntity httpEntity = null;
            IOException exception = null;
            for (int i = 0; i < 3; i++) {
                try {
                    CloseableHttpResponse response = httpClient.execute(request);
                    responseCode = response.getStatusLine().getStatusCode();
                    httpEntity = response.getEntity();

                    String responseBody = EntityUtils.toString(httpEntity, "utf-8");
                    if (responseBody != null) {
                        responseBytes = responseBody.getBytes();
                    } else {
                        InputStream respStream = null;
                        try {
                            respStream = httpEntity.getContent();
                            int respBodySize = respStream.available();
                            if (respBodySize <= 0) {
                                throw new IOException("Invalid respBodySize: " + respBodySize);
                            }
                            responseBytes = new byte[respBodySize];
                            if (respStream.read(responseBytes) != respBodySize) {
                                throw new IOException("Read respBody Error");
                            }
                        } catch (Exception e) {
                        } finally {
                            if (respStream != null) {
                                respStream.close();
                            }
                        }
                    }

                    exception = null;
                    break;
                } catch (UnsupportedOperationException e) {
                    try {
                        EntityUtils.consume(httpEntity);
                    } catch (IOException e2) {

                    }
                    throw new RuntimeException(e.getMessage(), e);
                } catch (IOException e) {
                    e.printStackTrace();
                    exception = e;
                    try {
                        EntityUtils.consume(httpEntity);
                    } catch (IOException e2) {

                    }
                    if (i < 2) {
                        try {
                            Thread.sleep(5);
                        } catch (InterruptedException e2) {

                        }
                    }
                }
            }
            if (exception != null) {
                throw exception;
            }

        }
        Map<String, Object> response = new HashMap<String, Object>();
        response.put("responseCode", responseCode);
        response.put("responseData", responseBytes);
        String loggerResponseString = getLoggerString(responseBytes, 256);
        return response;
    }

    private static Map<String, Object> sendGet(CloseableHttpClient httpClient, String url, Map<String, String> headers)
            throws IOException {
        String tag = "[HttpRequester] [GET " + url + "]";
        int responseCode = -1;
        byte[] responseBytes = null;

        HttpGet request = new HttpGet(url);
        if (headers != null && headers.size() > 0) {
            for (String name : headers.keySet()) {
                String value = headers.get(name);
                request.setHeader(name, value);
            }
        }
        HttpEntity httpEntity = null;
        IOException exception = null;
        for (int i = 0; i < 3; i++) {
            try {
                CloseableHttpResponse response = httpClient.execute(request);
                responseCode = response.getStatusLine().getStatusCode();
                httpEntity = response.getEntity();

                byte[] responseBody = EntityUtils.toByteArray(httpEntity);
                if (responseBody != null) {
                    responseBytes = responseBody;
                } else {
                    InputStream respStream = null;
                    try {
                        respStream = httpEntity.getContent();
                        int respBodySize = respStream.available();
                        if (respBodySize <= 0) {
                            throw new IOException("Invalid respBodySize: " + respBodySize);
                        }
                        responseBytes = new byte[respBodySize];
                        if (respStream.read(responseBytes) != respBodySize) {
                            throw new IOException("Read respBody Error");
                        }
                    } catch (Exception e) {
                    } finally {
                        if (respStream != null) {
                            respStream.close();
                        }
                    }
                }

                exception = null;
                break;
            } catch (UnsupportedOperationException e) {
                try {
                    EntityUtils.consume(httpEntity);
                } catch (IOException e2) {

                }
                throw new RuntimeException(e.getMessage(), e);
            } catch (IOException e) {
                e.printStackTrace();
                exception = e;
                try {
                    EntityUtils.consume(httpEntity);
                } catch (IOException e2) {

                }
                if (i < 2) {
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e2) {

                    }
                }
            }
        }
        if (exception != null) {
            throw exception;
        }
        Map<String, Object> response = new HashMap<String, Object>();
        response.put("responseCode", responseCode);
        response.put("responseData", responseBytes);
        String loggerResponseString = getLoggerString(responseBytes, 256);
        System.out.println(tag + " response " + responseCode + " " + loggerResponseString);
        return response;
    }

    private static String getLoggerString(final byte[] data, int maxLength) {
        String loggerString;
        if (data.length > maxLength) {
            byte[] shortData = new byte[maxLength];
            System.arraycopy(data, 0, shortData, 0, shortData.length);
            loggerString = new String(shortData, StandardCharsets.UTF_8) + "...";
        } else {
            loggerString = new String(data, StandardCharsets.UTF_8);
        }

        char[] chars = new char[loggerString.length()];
        loggerString.getChars(0, loggerString.length(), chars, 0);
        for (int i = 0; i < chars.length; i++) {
            char c = chars[i];
            if (c == '\n' || c == '\r') {
                chars[i] = ' ';
            }
        }
        return new String(chars);
    }

    private static CloseableHttpClient getHttpClient(String requestPath) {
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = null;
        Map<String, CloseableHttpClient> httpClients = new HashMap<String, CloseableHttpClient>();
        Object o = new Object();
        if (httpClients.containsKey(requestPath)) {
            return httpClients.get(requestPath);
        }
        if (poolingHttpClientConnectionManager == null) {
            synchronized (o) {
                if (poolingHttpClientConnectionManager == null) {
                    poolingHttpClientConnectionManager = createHttpClientConnectionManager();
                }
            }
        }
        synchronized (httpClients) {
            if (httpClients.containsKey(requestPath)) {
                return httpClients.get(requestPath);
            }
            CloseableHttpClient httpClient = createHttpClient(poolingHttpClientConnectionManager);
            httpClients.put(requestPath, httpClient);
            return httpClient;
        }
    }

    public static CloseableHttpClient createHttpClient(PoolingHttpClientConnectionManager connManager) {
        // HttpHost httpHost = new HttpHost("10.211.55.4", 8888);
        CloseableHttpClient httpClient = HttpClients.custom()
                // .setProxy(httpHost)
                .setConnectionManager(connManager).disableContentCompression().setSSLContext(getSslcontext())
                .setDefaultRequestConfig(getRequestConfig()).build();
        return httpClient;
    }

    public static PoolingHttpClientConnectionManager createHttpClientConnectionManager() {
        SSLConnectionSocketFactory sslConnectionSocketFactory = null;
        try {
            sslConnectionSocketFactory =
                    new SSLConnectionSocketFactory(getSslcontext(), SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("https", sslConnectionSocketFactory).register("http", new PlainConnectionSocketFactory()).build();
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        cm.setMaxTotal(500);
        cm.setDefaultMaxPerRoute(500);
        return cm;
    }

    private static RequestConfig getRequestConfig() {
        RequestConfig defaultRequestConfig =
                RequestConfig.custom().setConnectionRequestTimeout(6000).setSocketTimeout(30000).build();
        return defaultRequestConfig;
    }

    private static SSLContext getSslcontext() {
        SSLContext sslContext = null;
        try {
            sslContext = SSLContext.getInstance("TLS");
            TrustManager tm = new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            };
            sslContext.init(null, new TrustManager[] {tm}, null);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return sslContext;
    }

    /**
     * post请求传输json参数
     *
     * @param url
     *            url地址
     * @param reqJson
     *            参数
     * @return
     */
    public static String ouyePostJson(String url, String reqJson) {
        // post请求返回结果
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);
        // 设置请求和传输超时时间
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build();
        httpPost.setConfig(requestConfig);
        httpPost.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
        String respStr = "";
        try {
            if (null != reqJson) {
                // 解决中文乱码问题
                StringEntity entity = new StringEntity(reqJson, "utf-8");
                entity.setContentEncoding("UTF-8");
                entity.setContentType("application/json;charset=UTF-8");
                httpPost.setEntity(entity);
            }
            CloseableHttpResponse result = httpClient.execute(httpPost);
            System.out.println("result:" + result);
            System.out.println("httpPost:" + httpPost);
            System.out.println("HttpStatus.SC_OK:" + HttpStatus.SC_OK);
            System.out.println("result.getStatusLine().getStatusCode():" + result.getStatusLine().getStatusCode());
            // 请求发送成功，并得到响应
            if (result.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                try {
                    // 读取服务器返回过来的json字符串数据
                    respStr = EntityUtils.toString(result.getEntity(), "utf-8");
                    System.out.println("respStr1:" + respStr);
                } catch (Exception e) {
                    // logger.error("post请求提交失败:" + url, e);
                }
            } else {
                respStr = EntityUtils.toString(result.getEntity(), "utf-8");
                System.out.println("respStr2:" + respStr);
            }
        } catch (IOException e) {
            // logger.error("post请求提交失败:" + url, e);
        } finally {
            httpPost.releaseConnection();
        }
        return respStr;
    }
}
